#include "phong_shader.hh"

using namespace std;
using namespace cgmath;

phong_shader::phong_shader (const vector_3d& _specular, double n_phong_t,
                            bool use_importance_sampling_t)
  : specular (_specular), ns (n_phong_t),
    use_importance_sampling (use_importance_sampling_t)
{}

vector_3d phong_shader::bsdf (const surface_point& point,
				const vector_3d&   in_dir,
				const vector_3d&   out_dir)
{
  vector_3d c(0.0);
  double cos_theta = -cgmath::dot(in_dir, point.normal);
  if (cos_theta > 0)
    c = (cos_theta) * specular;
  else
    return c;
  
  // For a diffuse shader we would just scale c with pi and
  // return. For the phong shader, however, the outgoing radiance
  // is dependent on the angle between the ray of perfect reflection
  // and the direction of the viewer.

  // Perfect reflection.
  vector_3d r = 2 * (-dot(in_dir, point.normal)) * point.normal - in_dir;
  normalize (r);

  // todo: handle cos_phi < 0 ?
  double cos_phi = max(0.0, dot (r, out_dir));
  c *= pow(cos_phi, ns) * (ns + 1.0) / (2.0 * M_PI);

  return c;
}

void phong_shader::lambert_sample_bsdf (const surface_point& point,
                                        unsigned             num_samples,
                                        const vector_3d&     out_dir,
                                        bsdf_samples&        samples)
{
  samples.resize (num_samples);
  matrix_3d R;
  // The rotation aligns the surface normal with the z axis, so that
  // the sample will be on the upper sample in a coordinate system
  // where the normal is aligned with the z axis.
  R = rotate (vec(0.0, 0.0, 1.0), point.normal);
  for (unsigned i = 0; i < samples.size(); ++i)
    samples[i] = bsdf_sample (R * lambert_sample(), specular);
}

void phong_shader::sample_bsdf (const surface_point& point,
				  unsigned             num_samples,
				  const vector_3d&     out_dir,
				  bsdf_samples& samples)
{
  if (use_importance_sampling)
    {
      lambert_sample_bsdf (point, num_samples, out_dir, samples);
      return;
    }

  samples.resize (num_samples);
  matrix_3d R;

  vector_3d in_dir = 2 * (dot(out_dir, point.normal)) * point.normal - out_dir;
  normalize (in_dir);

  // The rotation aligns the surface normal with the z axis, so that
  // the sample will be on the upper sample in a coordinate system
  // where the normal is aligned with the z axis.
  R = rotate (vec(0.0, 0.0, 1.0), in_dir);
  
  for (unsigned i = 0; i < samples.size(); ++i)
    {
      vector_3d sample;

      // Generate samples around in_dir, which is the ray
      // of perfect reflection, until we obtain one that
      // is legal.
      do
        {
          sample = R * phong_sample(ns);
        }
      while (dot (sample, point.normal) < 0);
      normalize (sample);
      samples[i] = bsdf_sample (sample, specular);
    }
}
